package org.ru.mse10.cvis.service.search;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Root;

import org.ru.mse10.cvis.dto.BaseDTO;
import org.ru.mse10.cvis.entity.misc.Identity;

/**
 * The Class EntitySearcher.
 * 
 * @param <E>
 *            the element type
 * @param <T>
 *            the generic type
 */
public abstract class EntitySearcher<E extends Identity, T extends BaseDTO<E>> {

	/**
	 * Search.
	 * 
	 * @param entityManager
	 *            the entity manager
	 * @param dto
	 *            the dto
	 * @param countOnly
	 *            the count only
	 */
	public void search(EntityManager entityManager, T dto, boolean countOnly) {
		CriteriaBuilder qb = entityManager.getCriteriaBuilder();
		if (countOnly) {
			count(qb, dto, entityManager);
			return;
		}

		CriteriaQuery<E> c = qb.createQuery(getEntityClass());
		Root<E> p = c.from(getEntityClass());
		appendCriteria(qb, c, p, dto);
		TypedQuery<E> q = entityManager.createQuery(c);
		q.setFirstResult(dto.getFirstResult());
		q.setMaxResults(dto.getMaxResults());
		List<E> result = q.getResultList();
		dto.setResult(result);
	}

	/**
	 * Count.
	 * 
	 * @param qb
	 *            the qb
	 * @param dto
	 *            the dto
	 * @param entityManager
	 *            the entity manager
	 */
	private void count(CriteriaBuilder qb, T dto, EntityManager entityManager) {
		CriteriaQuery<Long> c = qb.createQuery(Long.class);
		Root<E> p = c.from(getEntityClass());
		appendCriteria(qb, c, p, dto);
		Expression<Long> count = qb.count(p);
		c.select(count);
		TypedQuery<Long> query = entityManager.createQuery(c);
		int totalResults = query.getSingleResult().intValue();
		dto.setTotalNumberOfRows(totalResults);
	}

	/**
	 * Append criteria.
	 * 
	 * @param <X>
	 *            the generic type
	 * @param qb
	 *            the qb
	 * @param c
	 *            the c
	 * @param p
	 *            the p
	 * @param dto
	 *            the dto
	 */
	protected abstract <X> void appendCriteria(CriteriaBuilder qb,
			CriteriaQuery<X> c, Root<E> p, T dto);

	/**
	 * Gets the entity class.
	 * 
	 * @return the entity class
	 */
	public abstract Class<E> getEntityClass();
}
